﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using SocialApi.Core.Request;
using SocialApi.Instagram.AppSettings;
using SocialApi.Core.Extensions;
using SocialApi.Instagram.Exceptions;

namespace SocialApi.Instagram.Entities.Methods.Locations
{
    internal class LocationMethods : ILocationMethods
    {
        #region Члены ILocationMethods

        /// <summary>
        /// Id локации
        /// </summary>
        public long Id { get; private set; }

        /// <summary>
        /// Широта
        /// </summary>
        public double Latitude { get; private set; }

        /// <summary>
        /// Долгота
        /// </summary>
        public double Longitude { get; private set; }

        /// <summary>
        /// Название локации
        /// </summary>
        public string Name { get; private set; }

        #region Информация о локации

        /// <summary>
        /// Возвращает информацию о локации
        /// </summary>
        /// <param name="locationId">Id локации</param>
        public ILocationMethods Get(long locationId)
        {
            var url = InstagramService.GetMethodUrl(ApiUrls.ApiBaseUrl, string.Format(ApiUrls.Location, locationId));
            var jObject = JObject.Parse(Navigator.Instance.Get(url));
            InstagramService.CheckResult(jObject);
            var result = jObject["data"].ToObject<LocationMethods>();
            SetData(result);
            return result;
        }

        /// <summary>
        /// Асинхронно возвращает информацию о локации
        /// </summary>
        /// <param name="locationId">Id локации</param>
        public async Task<ILocationMethods> GetAsync(long locationId)
        {
            var url = InstagramService.GetMethodUrl(ApiUrls.ApiBaseUrl, string.Format(ApiUrls.Location, locationId));
            var result = await Navigator.Instance.GetAsync(url);
            var jObject = JObject.Parse(result);
            InstagramService.CheckResult(jObject);
            var location = jObject["data"].ToObject<LocationMethods>();
            SetData(location);
            return location;
        }

        private void SetData(ILocationMethods location)
        {
            var properties = location.GetType().GetProperties();
            foreach (var propertyInfo in properties)
            {
                switch (propertyInfo.Name)
                {
#if NET_4_5
                    case "Id":
                        Id = propertyInfo.GetValue(location).ConvertTo<long>();
                        break;
                    case "Latitude":
                        Latitude = propertyInfo.GetValue(location).ConvertTo<double>();
                        break;
                    case "Longitude":
                        Longitude = propertyInfo.GetValue(location).ConvertTo<double>();
                        break;
                    case "Name":
                        Name = propertyInfo.GetValue(location).ToString();
                        break;
#else
                    case "Id":
                        Id = propertyInfo.GetValue(location, null).ConvertTo<long>();
                        break;
                    case "Latitude":
                        Latitude = propertyInfo.GetValue(location, null).ConvertTo<double>();
                        break;
                    case "Longitude":
                        Longitude = propertyInfo.GetValue(location, null).ConvertTo<double>();
                        break;
                    case "Name":
                        Name = propertyInfo.GetValue(location, null).ToString();
                        break;
#endif
                }
            }
        }

        #endregion

        #region Последнии локации

        /// <summary>
        /// Возвращает список последних медиа из данной локации
        /// </summary>
        /// <param name="locationId">Id локации</param>
        public IEnumerable<IResult> Recent(long locationId)
        {
            return Recent(locationId, -1, -1, null, null);
        }

        /// <summary>
        /// Возвращает список последних медиа из данной локации
        /// </summary>
        /// <param name="locationId">Id локации</param>
        /// <param name="minTimeStamp">Возвращает данные после заданого промежутка времени</param>
        /// <param name="maxTimeStamp">Возвращает данные перед заданым промежутком времени</param>
        public IEnumerable<IResult> Recent(long locationId, DateTime? minTimeStamp, DateTime? maxTimeStamp)
        {
            return Recent(locationId, -1, -1, minTimeStamp, maxTimeStamp);
        }

        /// <summary>
        /// Возвращает список последних медиа из данной локации
        /// </summary>
        /// <param name="locationId">Id локации</param>
        /// <param name="minId">Возвращает данные перед заданым Id</param>
        /// <param name="maxId">Возвращает данные после заданного Id</param>
        public IEnumerable<IResult> Recent(long locationId, int minId, int maxId)
        {
            return Recent(locationId, minId, minId, null, null);
        }

        /// <summary>
        /// Возвращает список последних медиа из данной локации
        /// </summary>
        /// <param name="locationId">Id локации</param>
        /// <param name="minId">Возвращает данные перед заданым Id</param>
        /// <param name="maxId">Возвращает данные после заданного Id</param>
        /// <param name="minTimeStamp">Возвращает данные после заданого промежутка времени</param>
        /// <param name="maxTimeStamp">Возвращает данные перед заданым промежутком времени</param>
        public IEnumerable<IResult> Recent(long locationId, int minId, int maxId, DateTime? minTimeStamp, DateTime? maxTimeStamp)
        {
            var url = CreateRecentUrl(locationId, minId, maxId, minTimeStamp, maxTimeStamp);
            var jObject = JObject.Parse(Navigator.Instance.Get(url));
            InstagramService.CheckResult(jObject);
#if NET_4_5 || NET_4_0
            return jObject["data"].ToObject<IEnumerable<Result>>();
#else
            return jObject["data"].ToObject<List<Result>>() as IEnumerable<IResult>;
#endif
        }

        /// <summary>
        /// Асинхронно возвращает список последних медиа из данной локации
        /// </summary>
        /// <param name="locationId">Id локации</param>
        public async Task<IEnumerable<IResult>> RecenthAsync(long locationId)
        {
            var result = await RecenthAsync(locationId, -1, -1, null, null);
            return result;
        }

        /// <summary>
        /// Асинхронно возвращает список последних медиа из данной локации
        /// </summary>
        /// <param name="locationId">Id локации</param>
        /// <param name="minTimeStamp">Возвращает данные после заданого промежутка времени</param>
        /// <param name="maxTimeStamp">Возвращает данные перед заданым промежутком времени</param>
        public async Task<IEnumerable<IResult>> RecenthAsync(long locationId, DateTime? minTimeStamp, DateTime? maxTimeStamp)
        {
            var result = await RecenthAsync(locationId, -1, -1, minTimeStamp, maxTimeStamp);
            return result;
        }

        /// <summary>
        /// Асинхронно возвращает список последних медиа из данной локации
        /// </summary>
        /// <param name="locationId">Id локации</param>
        /// <param name="minId">Возвращает данные перед заданым Id</param>
        /// <param name="maxId">Возвращает данные после заданного Id</param>
        public async Task<IEnumerable<IResult>> RecenthAsync(long locationId, int minId, int maxId)
        {
            var result = await RecenthAsync(locationId, minId, maxId, null, null);
            return result;
        }

        /// <summary>
        /// Асинхронно возвращает список последних медиа из данной локации
        /// </summary>
        /// <param name="locationId">Id локации</param>
        /// <param name="minId">Возвращает данные перед заданым Id</param>
        /// <param name="maxId">Возвращает данные после заданного Id</param>
        /// <param name="minTimeStamp">Возвращает данные после заданого промежутка времени</param>
        /// <param name="maxTimeStamp">Возвращает данные перед заданым промежутком времени</param>
        public async Task<IEnumerable<IResult>> RecenthAsync(long locationId, int minId, int maxId, DateTime? minTimeStamp, DateTime? maxTimeStamp)
        {
            var url = CreateRecentUrl(locationId, minId, maxId, minTimeStamp, maxTimeStamp);
            var result = await Navigator.Instance.GetAsync(url);
            var jObject = JObject.Parse(result);
            InstagramService.CheckResult(jObject);
#if NET_4_5 || NET_4_0
            return jObject["data"].ToObject<IEnumerable<Result>>();
#else
            return jObject["data"].ToObject<List<Result>>() as IEnumerable<IResult>;
#endif
        }

        private static string CreateRecentUrl(long locationId, int minId, int maxId, DateTime? minTimeStamp, DateTime? maxTimeStamp)
        {
            var result = new StringBuilder(InstagramService.GetMethodUrl(ApiUrls.ApiBaseUrl, string.Format(ApiUrls.Location, locationId), "/", ApiUrls.MediaRecent));
            if (minId.Exists())
            {
                result.AppendFormat("&min_id={0}", minId);
            }
            if (maxId.Exists())
            {
                result.AppendFormat("&max_id={0}", maxId);
            }
            if (minTimeStamp.Exists())
            {
                if (minTimeStamp.HasValue)
                {
                    result.AppendFormat("&min_timestamp={0}", minTimeStamp.Value.ToUnixTimestamp());
                }
            }
            if (maxTimeStamp.Exists())
            {
                if (maxTimeStamp.HasValue)
                {
                    result.AppendFormat("&max_timestamp={0}", maxTimeStamp.Value.ToUnixTimestamp());
                }
            }
            return result.ToString();
        }

        #endregion

        #region Поиск

        /// <summary>
        /// Поиск местоположения по заданным географическим координатам
        /// </summary>
        /// <param name="latitude">Широта</param>
        /// <param name="longitude">Долгота</param>
        public IEnumerable<ILocationMethods> Search(double latitude, double longitude)
        {
            return Search(1000, latitude, longitude);
        }

        /// <summary>
        /// Поиск местоположения по заданным географическим координатам
        /// </summary>
        /// <param name="distance">Дистанция (от 1000 до 5000)</param>
        /// <param name="latitude">Широта</param>
        /// <param name="longitude">Долгота</param>
        public IEnumerable<ILocationMethods> Search(int distance, double latitude, double longitude)
        {
            if (distance < 1000 || distance > 5000)
            {
                throw new IntsagramApiException("Дистанция должна быть от 1000 до 5000");
            }
            var url = CreateSearchUrl(distance, latitude, latitude);
            var jObject = JObject.Parse(Navigator.Instance.Get(url));
            InstagramService.CheckResult(jObject);
#if NET_4_5 || NET_4_0
            return jObject["data"].ToObject<IEnumerable<LocationMethods>>();
#else
            return jObject["data"].ToObject<List<LocationMethods>>() as IEnumerable<ILocationMethods>;
#endif
        }

        /// <summary>
        /// Асинхронный поиск местоположения по заданным географическим координатам
        /// </summary>
        /// <param name="latitude">Широта</param>
        /// <param name="longitude">Долгота</param>
        public async Task<IEnumerable<ILocationMethods>> SearchAsync(double latitude, double longitude)
        {
            var result = await SearchAsync(1000, latitude, longitude);
            return result;
        }

        /// <summary>
        /// Асинхронный поиск местоположения по заданным географическим координатам
        /// </summary>
        /// <param name="distance">Дистанция (от 1000 до 5000)</param>
        /// <param name="latitude">Широта</param>
        /// <param name="longitude">Долгота</param>
        public async Task<IEnumerable<ILocationMethods>> SearchAsync(int distance, double latitude, double longitude)
        {
            if (distance < 1000 || distance > 5000)
            {
                throw new IntsagramApiException("Дистанция должна быть от 1000 до 5000");
            }
            var url = CreateSearchUrl(distance, latitude, latitude);
            var result = await Navigator.Instance.GetAsync(url);
            var jObject = JObject.Parse(result);
            InstagramService.CheckResult(jObject);
#if NET_4_5 || NET_4_0
            return jObject["data"].ToObject<IEnumerable<LocationMethods>>();
#else
            return jObject["data"].ToObject<List<LocationMethods>>() as IEnumerable<ILocationMethods>;
#endif
        }

        private static string CreateSearchUrl(int distance, double latitude, double longitude)
        {
            var result = new StringBuilder(InstagramService.GetMethodUrl(ApiUrls.ApiBaseUrl, string.Format(ApiUrls.Location, ApiUrls.Search)));
            result.AppendFormat("&distance={0}", distance);
            if (latitude != 0)
            {
                result.AppendFormat("&lat={0}", latitude);
            }
            if (longitude != 0)
            {
                result.AppendFormat("&lng={0}", longitude);
            }
            return result.ToString();
        }

        #endregion

        #endregion
    }
}